using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using InterfaceExtractor.Targets;
using ICSharpCode.NRefactory.Ast;
using System.IO;
using ICSharpCode.NRefactory;
using Genuilder;
using AstHelpers;

namespace InterfaceExtractor.Tests
{
	[TestClass]
	public class InterfaceExtractorTest
	{
		Tester tester = new Tester();
		public class Tester
		{
			const string DataDirectory = "../../../InterfaceExtractor.Tests/Data";


			IEnumerable<CompilationUnit> _TestedUnits;
			public IEnumerable<CompilationUnit> TestedUnits
			{
				get
				{
					if(_TestedUnits == null)
					{
						_TestedUnits = GetTestedCompilationUnits(DataDirectory).ToList();
					}
					return _TestedUnits;
				}
			}

			IEnumerable<CompilationUnit> _CreatedUnits;
			public IEnumerable<CompilationUnit> CreatedUnits
			{
				get
				{
					if(_CreatedUnits == null)
					{
						_CreatedUnits = TestedUnits.Select(u =>
						{
							var generator = new InterfaceExtractorGenerator();
							u.AcceptVisitor(generator, null);
							return generator.CompilationUnit;
						}).ToList();
					}
					return _CreatedUnits;
				}
			}

			IEnumerable<TypeDeclaration> _TestedTypes;
			public IEnumerable<TypeDeclaration> TestedTypes
			{
				get
				{
					if(_TestedTypes == null)
						_TestedTypes = TestedUnits.SelectMany(unit => GetTypeDeclarations(unit)).ToList();
					return _TestedTypes;
				}
			}

			IEnumerable<KeyValuePair<TypeDeclaration, TypeDeclaration>> _TestedAndGeneratedTypes;
			public IEnumerable<KeyValuePair<TypeDeclaration, TypeDeclaration>> TestedAndGeneratedTypes
			{
				get
				{
					if(_TestedAndGeneratedTypes == null)
					{
						var result = new List<KeyValuePair<TypeDeclaration, TypeDeclaration>>();
						foreach(var testedType in TestedTypes)
						{
							var name = InterfaceExtractorGenerator.GetGeneratedTypeNameFor(testedType);
							var createdType = CreatedTypes.SingleOrDefault(t => t.Name == name);
							result.Add(new KeyValuePair<TypeDeclaration, TypeDeclaration>(testedType, createdType));
						}
						_TestedAndGeneratedTypes = result;
					}
					return _TestedAndGeneratedTypes;
				}
			}

			private IEnumerable<CompilationUnit> GetTestedCompilationUnits(string directory)
			{
				List<CompilationUnit> result = new List<CompilationUnit>();
				DirectoryInfo dir = new DirectoryInfo(directory);
				foreach(var subDir in dir.GetDirectories())
				{
					result.AddRange(GetTestedCompilationUnits(subDir.FullName));
				}
				foreach(var file in dir.GetFiles("*.cs"))
				{
					result.Add(GetTestedCompilationUnits(file));
				}
				return result;
			}

			private CompilationUnit GetTestedCompilationUnits(FileInfo file)
			{
				var parser = ParserFactory.CreateParser(file.FullName);
				parser.Parse();
				return parser.CompilationUnit;
			}

			private IEnumerable<TypeDeclaration> GetTypeDeclarations(INode node)
			{
				var result = new List<TypeDeclaration>();
				GetTypeDeclarations(node, result);
				return result;
			}
			private void GetTypeDeclarations(INode node, List<TypeDeclaration> types)
			{
				if(node is TypeDeclaration)
					types.Add((TypeDeclaration)node);
				foreach(var child in node.Children)
					GetTypeDeclarations(child, types);
			}

			IEnumerable<TypeDeclaration> _CreatedTypes;
			public IEnumerable<TypeDeclaration> CreatedTypes
			{
				get
				{
					if(_CreatedTypes == null)
					{
						_CreatedTypes = CreatedUnits.SelectMany(u => GetTypeDeclarations(u)).ToList();
					}
					return _CreatedTypes;
				}
			}
		}


		public bool HasExtractInterfaceAttribute(TypeDeclaration declaration)
		{
			return GetExtractInterfaceAttribute(declaration) != null;
		}

		public ICSharpCode.NRefactory.Ast.Attribute GetExtractInterfaceAttribute(TypeDeclaration declaration)
		{
			return declaration.Attributes.SelectMany(attrs => attrs.Attributes).FirstOrDefault(attr => attr.Name == typeof(ExtractInterface).Name);
		}

		[TestMethod]
		public void AssertInterfacesKeepAttributes()
		{
			foreach(var pair in tester.TestedAndGeneratedTypes.Where(p => p.Value != null))
			{
				foreach(var testMember in pair.Key.Children.OfType<MemberNode>().Where(e => e.Modifier.IsPublic() && e.GetType() != typeof(FieldDeclaration)))
				{
					foreach(var attribute in testMember.Attributes.SelectMany(a => a.Attributes))
					{
						var generatedMember = GetMember(pair.Value, testMember);
						if(generatedMember == null)
							continue;

						AssertAttributeIsPresent(generatedMember, attribute);
					}
				}
			}
		}

		private void AssertAttributeIsPresent(MemberNode generatedMember, ICSharpCode.NRefactory.Ast.Attribute attribute)
		{
			Assert.IsTrue(AttributeIsPresent(generatedMember, attribute), "attribute " + attribute.Name + " is not present in the generated type");
		}
		private bool AttributeIsPresent(MemberNode generatedMember, ICSharpCode.NRefactory.Ast.Attribute attribute)
		{
			return generatedMember.Attributes.SelectMany(a => a.Attributes).Any(a => a.Name == attribute.Name);
		}


		[TestMethod]
		public void AssertGeneratePublicMembers()
		{
			foreach(var pair in tester.TestedAndGeneratedTypes.Where(p => p.Value != null))
			{
				foreach(var testMember in pair.Key.Children.OfType<MemberNode>().Where(e => e.Modifier.IsPublic() && e.GetType() != typeof(FieldDeclaration)))
				{
					if(testMember.Modifier.IsPublic())
						AssertHasMember(pair.Value, testMember);
					else
						AssertHasNotMember(pair.Value, testMember);
				}
			}
		}

		[TestMethod]
		public void AssertGeneratePublicPropertyAccessor()
		{
			foreach(var pair in tester.TestedAndGeneratedTypes.Where(p => p.Value != null))
			{
				foreach(var property in pair.Key.Children.OfType<PropertyDeclaration>().Where(e => e.Modifier.IsPublic()))
				{
					var getSet = new Func<PropertyDeclaration, PropertyGetSetRegion>[] { (prop) => prop.GetRegion, (prop) => prop.SetRegion };
					foreach(var selectRegion in getSet)
					{
						var region = selectRegion(property);
						var generatedRegion = selectRegion((PropertyDeclaration)GetMember(pair.Value, property));
						//var generatedProperty = (PropertyDeclaration)GetMember(pair.Value, property);

						if(region.IsNull)
						{
							Assert.IsTrue(generatedRegion.IsNull);
						}
						else if(region.Modifier.IsPublic() || region.Modifier == Modifiers.None)
						{
							Assert.IsFalse(generatedRegion.IsNull, "generated region should not be null");
						}
						else
						{
							Assert.IsTrue(generatedRegion.IsNull, "generated region should be null");
						}
					}
				}
			}
		}

		void AssertHasMember(TypeDeclaration decl, MemberNode node)
		{
			Assert.IsTrue(HasMember(decl, node), "member " + node.Name + " should be present in " + decl.Name);
		}
		void AssertHasNotMember(TypeDeclaration decl, MemberNode node)
		{
			Assert.IsFalse(HasMember(decl, node), "member " + node.Name + " should not be present in " + decl.Name);
		}
		bool HasMember(TypeDeclaration typeDeclaration, MemberNode member)
		{
			var hasMember = GetMember(typeDeclaration, member) != null;
			return hasMember;
		}

		MemberNode GetMember(TypeDeclaration decl, MemberNode node)
		{
			return decl.Children.OfType<MemberNode>().FirstOrDefault(m => m.Name == node.Name);
		}
		[TestMethod]
		public void DoNotGenerateWhenNoExtractInterface()
		{
			foreach(var pair in tester.TestedAndGeneratedTypes)
			{
				if(!HasExtractInterfaceAttribute(pair.Key))
					Assert.IsNull(pair.Value);
			}
		}
		[TestMethod]
		public void GenerateWhenExtractInterface()
		{
			foreach(var pair in tester.TestedAndGeneratedTypes)
			{
				if(HasExtractInterfaceAttribute(pair.Key))
					Assert.IsNotNull(pair.Value);
			}
		}
		[TestMethod]
		public void CanChangeExtractInterfaceName()
		{
			foreach(var pair in tester.TestedAndGeneratedTypes.Where(p => p.Value != null))
			{
				var expectedName = InterfaceExtractorGenerator.GetGeneratedTypeNameFor(pair.Key);
				Assert.AreEqual(expectedName, pair.Value.Name);
			}
		}
	}
}
